﻿using System;
using System.Runtime.InteropServices;
using System.Text;
using DatabaseGroup.SSIS.SourceAdapters.Parser;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;

namespace DatabaseGroup.SSIS.SourceAdapters
{
    /// <summary>
    /// Core delimited file source class, implementing all the features
    /// that SSIS will use and connect to
    /// </summary>
    [DtsPipelineComponent(
        DisplayName = "DFS 2005",
        CurrentVersion = 7,
        ComponentType = ComponentType.SourceAdapter,
        IconResource = "DatabaseGroup.SSIS.SourceAdapters.DelimitedFileSource.ico",
        UITypeName = "DatabaseGroup.SSIS.SourceAdapters.DelimitedFileSource2005ComponentUI, DatabaseGroup.SSIS.SourceAdapters.DelimitedFileSource2005, Version=1.0.7.0, Culture=neutral, PublicKeyToken=103567041829a1c2"
        )]
    public class DelimitedFileSource2005Component : PipelineComponent
    {
        #region Constants

        /// <summary>
        /// The column name used to contain the line number in the source file
        /// </summary>
        internal const string LineNumberColumnName = "LineNumber";

        /// <summary>
        /// The column name used to contain the datetime the row was added
        /// </summary>
        internal const string OccurredColumnName = "Occurred";

        /// <summary>
        /// The column name used to contain the row data
        /// </summary>
        internal const string RowDataColumnName = "RowData";

        /// <summary>
        /// The column name used to contain the flag indicating if the column was 
        /// fatal to processing the standard output
        /// </summary>
        internal const string IsInvalidColumnName = "IsInvalid";

        /// <summary>
        /// The column name used to contain the message for the log/error column
        /// </summary>
        internal const string DescriptionColumnName = "Description";

        /// <summary>
        /// The column name used to contain the source file column name
        /// </summary>
        internal const string SourceColumnName = "SourceColumnName";

        /// <summary>
        /// The column name used to contain the source file column data
        /// </summary>
        internal const string SourceColumnDataColumnName = "SourceColumnData";

        /// <summary>
        /// The column name used to contain the datatype of the target 
        /// column of the standard output
        /// </summary>
        internal const string TargetColumnDataTypeColumnName = "TargetColumnDataType";

        /// <summary>
        /// The column name used to contain the length of the target
        /// column of the standard output
        /// </summary>
        internal const string TargetColumnLengthColumnName = "TargetColumnLength";

        /// <summary>
        /// The column name used to contain the error code
        /// </summary>
        internal const string ErrorCodeColumnName = "ErrorCode";

        /// <summary>
        /// The column name used to contain the validation message
        /// </summary>
        internal const string ValidationMessageColumnName = "Description";

        private const int _E_FAIL = unchecked((int)0x80004005);
        private const string _fileConnectionName = "DelimitedFile";
        private const int _defaultStringColumnSize = 255;

        #endregion

        #region Data members

        private string _fileName;
        private readonly PropertiesManager _propertiesManager = new PropertiesManager();
        private OutputRuntimePropertiesCache _mainOutputProps = null;
        private OutputRuntimePropertiesCache _logOutputProps = null;
        private OutputRuntimePropertiesCache _errorOutputProps = null;

        #endregion

        /// <summary>
        /// Initializes a new instance of the DelimitedFileSourceComponent class
        /// </summary>
        public DelimitedFileSource2005Component()
        {
            _propertiesManager.PostErrorEvent += PostError;
        }

        #region PipelineComponent overrides

        /// <summary>
        /// Called when an earlier version of the component is opened 
        /// within a package - this upgrades the old component metadata
        /// to match the latest version of the component
        /// </summary>
        /// <param name="pipelineVersion">The version of the pipeline the component is hosted within</param>
        public override void PerformUpgrade(int pipelineVersion)
        {
            // Obtain the current component version from the attribute.
            DtsPipelineComponentAttribute componentAttribute =
              (DtsPipelineComponentAttribute)Attribute.GetCustomAttribute(this.GetType(), typeof(DtsPipelineComponentAttribute), false);
            int currentVersion = componentAttribute.CurrentVersion;

            UpgradeHelper.Upgrade(this.ComponentMetaData, currentVersion);

            this.ComponentMetaData.CustomPropertyCollection["UserComponentTypeName"].Value =
                typeof(DelimitedFileSource2005Component).AssemblyQualifiedName;
            this.ComponentMetaData.Version = currentVersion;
        }

        /// <summary>
        /// Initializes the component with default settings
        /// </summary>
        /// <remarks>Called by SSIS when the component is first added to a dataflow task</remarks>
        public override void ProvideComponentProperties()
        {
            this.RemoveAllInputsOutputsAndCustomProperties();
            this.ComponentMetaData.UsesDispositions = true;

            DtsPipelineComponentAttribute componentAttribute =
              (DtsPipelineComponentAttribute)Attribute.GetCustomAttribute(this.GetType(), typeof(DtsPipelineComponentAttribute), false);
            int currentVersion = componentAttribute.CurrentVersion;

            ComponentMetaData.Version = currentVersion;
            ComponentMetaData.ContactInfo = MessageStrings.ContactInfo;

            PropertiesManager.AddComponentProperties(this.ComponentMetaData.CustomPropertyCollection);

            IDTSOutput90 standardOutput = this.ComponentMetaData.OutputCollection.New();
            standardOutput.Name = MessageStrings.OutputName;
            standardOutput.ErrorRowDisposition = DTSRowDisposition.RD_FailComponent;
            standardOutput.TruncationRowDisposition = DTSRowDisposition.RD_RedirectRow;
            standardOutput.ErrorOrTruncationOperation = MessageStrings.RowLevelTruncationOperation;

            IDTSOutput90 logOutput = this.ComponentMetaData.OutputCollection.New();
            logOutput.Name = MessageStrings.LogOutputName;
            AddLogOutputColumns(logOutput);

            IDTSOutput90 errorOutput = this.ComponentMetaData.OutputCollection.New();
            errorOutput.IsErrorOut = true;
            errorOutput.Name = MessageStrings.ErrorOutputName;
            AddErrorOutputColumns(errorOutput);

            // Reserve space for the file connection.
            IDTSRuntimeConnection90 connectionSlot = this.ComponentMetaData.RuntimeConnectionCollection.New();
            connectionSlot.Name = _fileConnectionName;
            connectionSlot.Description = MessageStrings.FileConnectionDescription;
        }

        /// <summary>
        /// Adds a column to an output of the component 
        /// </summary>
        /// <param name="outputID">The ID of the output to add the column to</param>
        /// <param name="outputColumnIndex">The position to insert the column at</param>
        /// <param name="name">The name of the new column</param>
        /// <param name="description">The description of the new column</param>
        /// <remarks>Called when the SSIS provided advance editor attempts to add an output column</remarks>
        /// <returns>The inserted column</returns>
        public override IDTSOutputColumn90 InsertOutputColumnAt(int outputID, int outputColumnIndex, string name, string description)
        {
            IDTSOutputColumn90 outputColumn = null;
            IDTSOutput90 output = this.ComponentMetaData.OutputCollection.FindObjectByID(outputID);
            
            if (output != null)
            {
                // log and error outputs are static and cannot be changed manually
                if (output.IsErrorOut)
                {
                    PostErrorAndThrow(MessageStrings.CantAddColumnsToErrorOutput);
                }
                else if (output.Name == MessageStrings.LogOutputName)
                {
                    PostErrorAndThrow(MessageStrings.CantAddColumnsToLogOutput);
                }
                else
                {
                    if (_mainOutputProps.Contains(name))
                        PostErrorAndThrow(MessageStrings.OutputColumnNameAlreadyExists);
                    
                    outputColumn = base.InsertOutputColumnAt(outputID, outputColumnIndex, name, description);
                    outputColumn.ErrorRowDisposition = DTSRowDisposition.RD_FailComponent;
                    outputColumn.TruncationRowDisposition = DTSRowDisposition.RD_FailComponent;
                    outputColumn.ErrorOrTruncationOperation = MessageStrings.ColumnLevelErrorTruncationOperation;
                    outputColumn.SetDataTypeProperties(DataType.DT_WSTR, _defaultStringColumnSize, 0, 0, 0);
                }
            }

            return outputColumn;
        }

        /// <summary>
        /// Establishes a connection to a connection manager
        /// </summary>
        /// <param name="transaction">The optional transaction the connection is participating in</param>
        /// <remarks>Called by SSIS during both design and execution phases</remarks>
        public override void AcquireConnections(object transaction)
        {
            DTSValidationStatus validationStatus = ValidateConnection(DTSValidationStatus.VS_ISVALID);
            if (validationStatus == DTSValidationStatus.VS_ISVALID)
            {
                IDTSConnectionManager90 connectionManager = this.ComponentMetaData.RuntimeConnectionCollection[0].ConnectionManager;
                try
                {
                    _fileName = (string)connectionManager.AcquireConnection(transaction);
                }
                catch (Exception)
                {
                    PostErrorAndThrow(MessageStrings.CannotGetFileNameFromConnection);
                }

                if (!System.IO.File.Exists(_fileName))
                {
                    if (GetComponentPropertyValue<bool>(PropertiesManager.DelayValidationPropName))
                        return;

                    PostErrorAndThrow(MessageStrings.FileDoesNotExist(_fileName));
                }
            }
            else
            {
                throw new COMException(string.Empty, _E_FAIL);
            }
        }

        /// <summary>
        /// Assigns a value to one of the components custom properties
        /// </summary>
        /// <param name="propertyName">The name of the custom property to assign the value to</param>
        /// <param name="propertyValue">The value to assign to the custom property</param>
        /// <returns>The custom property that was modified</returns>
        public override IDTSCustomProperty90 SetComponentProperty(string propertyName, object propertyValue)
        {
            if (_propertiesManager.ValidatePropertyValue(propertyName, propertyValue, DTSValidationStatus.VS_ISVALID) == DTSValidationStatus.VS_ISVALID)
                return base.SetComponentProperty(propertyName, propertyValue);
            
            throw new COMException(string.Empty, _E_FAIL);
        }

        /// <summary>
        /// Sets the data type properties of a component output column 
        /// </summary>
        /// <param name="iOutputID">The ID of the output the column is hosted within</param>
        /// <param name="iOutputColumnID">The ID of the output column to set</param>
        /// <param name="eDataType">The datatype to set the column to</param>
        /// <param name="iLength">The column length (for text datatypes)</param>
        /// <param name="iPrecision">The column precision (for decimal based datatypes)</param>
        /// <param name="iScale">The column scale (for decimal based datatypes)</param>
        /// <param name="iCodePage">The column codepage (for text datatypes)</param>
        public override void SetOutputColumnDataTypeProperties(int iOutputID, int iOutputColumnID, DataType eDataType, int iLength, int iPrecision, int iScale, int iCodePage)
        {
            IDTSOutputCollection90 outputColl = ComponentMetaData.OutputCollection;
            IDTSOutput90 output = outputColl.GetObjectByID(iOutputID);
            if (output != null)
            {
                if (output.IsErrorOut)
                {
                    PostErrorAndThrow(MessageStrings.CantChangeErrorOutputProperties);
                }
                else if (output.Name == MessageStrings.LogOutputName)
                {
                    PostErrorAndThrow(MessageStrings.CantChangeLogOutputProperties);
                }
                else
                {
                    IDTSOutputColumnCollection90 columnColl = output.OutputColumnCollection;
                    IDTSOutputColumn90 column = columnColl.GetObjectByID(iOutputColumnID);
                    if (column != null)
                    {
                        if (ValidateSupportedDataTypes(eDataType) == DTSValidationStatus.VS_ISVALID)
                        {
                            column.SetDataTypeProperties(eDataType, iLength, iPrecision, iScale, iCodePage);
                        }
                        else
                        {
                            throw new COMException(string.Empty, _E_FAIL);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Verifies that the component is correctly configured
        /// </summary>
        /// <remarks>Called repeatedly by SSIS during design time for validation and once before execution</remarks>
        /// <returns>The status of the validation</returns>
        public override DTSValidationStatus Validate()
        {
            DTSValidationStatus status = DTSValidationStatus.VS_ISVALID;

            status = ValidateComponentProperties(status);
            status = ValidateInputs(status);
            status = ValidateOutputs(status);

            return status;
        }

        /// <summary>
        /// Prepares the component for the execution processing by 
        /// caching all the settings for quick access
        /// </summary>
        /// <remarks>Called by SSIS after PrepareForExecute, and before PrimeOutput and ProcessInput</remarks>
        public override void PreExecute()
        {
            foreach (IDTSOutput90 output in ComponentMetaData.OutputCollection)
            {
                switch (output.Name)
                {
                    case MessageStrings.OutputName:
                        _mainOutputProps = new OutputRuntimePropertiesCache(output, BufferManager);
                        break;
                    case MessageStrings.LogOutputName:
                        _logOutputProps = new OutputRuntimePropertiesCache(output, BufferManager);
                        break;
                    case MessageStrings.ErrorOutputName:
                        _errorOutputProps = new OutputRuntimePropertiesCache(output, BufferManager);
                        break;
                }
            }

            //System.Diagnostics.Debugger.Break();
        }

        /// <summary>
        /// Does the key work of the component by cycling through the source
        /// file and adding the rows to the output buffers
        /// </summary>
        /// <param name="outputs">The number of outputs configured on the component</param>
        /// <param name="outputIDs">A list of IDs for the outputs configured on the component</param>
        /// <param name="buffers">A list of buffers, one for each output, in the same order as the output IDs</param>
        /// <remarks>
        /// SSIS calls this method for processing of asynchronous outputs 
        /// (all outputs of this component are asynchronous)
        /// </remarks>
        public override void PrimeOutput(int outputs, int[] outputIDs, PipelineBuffer[] buffers)
        {
            IDTSOutput90 mainOutput = ComponentMetaData.OutputCollection[MessageStrings.OutputName];
            
            for (int outputIndex = 0; outputIndex < outputs; outputIndex++)
            {
                if (_mainOutputProps != null && _mainOutputProps.OutputID == outputIDs[outputIndex])
                    _mainOutputProps.Buffer = buffers[outputIndex];
                else if (_logOutputProps != null && _logOutputProps.OutputID == outputIDs[outputIndex])
                    _logOutputProps.Buffer = buffers[outputIndex];
                else if (_errorOutputProps != null && _errorOutputProps.OutputID == outputIDs[outputIndex])
                    _errorOutputProps.Buffer = buffers[outputIndex];
            }

            FileReader reader = new FileReader(_fileName, GetEncoding());

            string columnDelimiter = GetComponentPropertyValue<string>(PropertiesManager.ColumnDelimiterPropName);
            string rowDelimiter = GetComponentPropertyValue<string>(PropertiesManager.RowDelimiterPropName);

            DelimitedFileParser parser = 
                new DelimitedFileParser(columnDelimiter, rowDelimiter)
                {
                    ColumnNameInFirstRow = GetComponentPropertyValue<bool>(PropertiesManager.ColumnNamesInFirstRowPropName),
                    DataRowsToSkip = GetComponentPropertyValue<int>(PropertiesManager.DataRowsToSkipPropName),
                    HeaderRowDelimiter = GetComponentPropertyValue<string>(PropertiesManager.HeaderRowDelimiterPropName),
                    HeaderRowsToSkip = GetComponentPropertyValue<int>(PropertiesManager.HeaderRowsToSkipPropName),
                    TextQualifier = GetComponentPropertyValue<string>(PropertiesManager.TextQualifierPropName)
                };
            
            BufferSink bufferSink = CreateBufferSink(mainOutput, parser);            
            
            try
            {
                parser.SkipInitialRows(reader);

                RowData rowData = new RowData();
                while (!reader.IsEOF)
                {
                    parser.ParseNextRow(reader, rowData);
                    if (rowData.ColumnCount == 0)
                    {
                        // TODO: check this when empty line in file
                        // Last row with no data will be ignored.
                        break;
                    }
                    bufferSink.AddRow(rowData);
                }
            }
            catch (ParsingBufferOverflowException ex)
            {
                PostErrorAndThrow(MessageStrings.ParsingBufferOverflow(bufferSink.CurrentRowCount + 1, ex.ColumnIndex + 1, FieldParser.ParsingBufferMaxSize));
            }
            catch (RowColumnNumberOverflow)
            {
                PostErrorAndThrow(MessageStrings.MaximumColumnNumberOverflow(bufferSink.CurrentRowCount + 1, RowParser.MaxColumnNumber));
            }
            finally
            {
                reader.Close();
            }

            foreach (PipelineBuffer buffer in buffers)
            {
                buffer.SetEndOfRowset();
            }
        }

        /// <summary>
        /// Inserts an input for the component
        /// </summary>
        /// <param name="insertPlacement">The location to add the insert to</param>
        /// <param name="inputID">The ID of the insert to add</param>
        /// <remarks>This component is a source and therefore cannot have inputs</remarks>
        /// <returns>The inserted input</returns>
        public override IDTSInput90 InsertInput(DTSInsertPlacement insertPlacement, int inputID)
        {
            PostErrorAndThrow(MessageStrings.CantAddInput);
            return null;
        }

        /// <summary>
        /// Inserts an output for the component
        /// </summary>
        /// <param name="insertPlacement">The location to add the output to</param>
        /// <param name="outputID">The ID of the output to add</param>
        /// <remarks>This component has fixed outputs that should be configured only via the main UI</remarks>
        /// <returns>The inserted output</returns>
        public override IDTSOutput90 InsertOutput(DTSInsertPlacement insertPlacement, int outputID)
        {
            PostErrorAndThrow(MessageStrings.CantAddOutput);
            return null;
        }

        /// <summary>
        /// Inserts a new external metadata column at the specified index
        /// </summary>
        /// <param name="iID">The ID of the output to insert the column to</param>
        /// <param name="iExternalMetadataColumnIndex">The index at which the column should be inserted</param>
        /// <param name="strName">The name of the column</param>
        /// <param name="strDescription">The description of the column</param>
        /// <remarks>
        /// This component manages all the external metadata column itself 
        /// and should not be done directly
        /// </remarks>
        /// <returns>The inserted column</returns>
        public override IDTSExternalMetadataColumn90 InsertExternalMetadataColumnAt(int iID, int iExternalMetadataColumnIndex, string strName, string strDescription)
        {
            PostErrorAndThrow(MessageStrings.CantAddExternalColumns);
            return null;
        }

        /// <summary>
        /// Deletes a specific input
        /// </summary>
        /// <param name="inputID">The ID of the input to delete</param>
        /// <remarks>This is a source component and therefore cannot have any inputs</remarks>
        public override void DeleteInput(int inputID)
        {
            PostErrorAndThrow(MessageStrings.CantDeleteInput);
        }

        /// <summary>
        /// Delete a specific output
        /// </summary>
        /// <param name="outputID">The ID of the output to delete</param>
        /// <remarks>
        /// This component manages its outputs via the main UI 
        /// and they should no be deleted directly
        /// </remarks>
        public override void DeleteOutput(int outputID)
        {
            PostErrorAndThrow(MessageStrings.CantDeleteOutput);
        }

        /// <summary>
        /// Deletes a specific external metadata column
        /// </summary>
        /// <param name="iID">The ID of the output containing the column</param>
        /// <param name="iExternalMetadataColumnID">The ID of the column to delete</param>
        /// <remarks>
        /// This component manages its external metadata 
        /// columns internally and should not be deleted directly
        /// </remarks>
        public override void DeleteExternalMetadataColumn(int iID, int iExternalMetadataColumnID)
        {
            PostErrorAndThrow(MessageStrings.CantDeleteExternalColumns);
        }

        /// <summary>
        /// Converts an integer error code to its user friendly string version
        /// </summary>
        /// <param name="iErrorCode">The error code to describe</param>
        /// <remarks>
        /// This is typically called by SSIS when using the data grid view during package execution 
        /// (an error description column is added automatically by SSIS to the error log data grid view) 
        /// </remarks>
        /// <returns>The user friendly string describing the error</returns>
        public override string DescribeRedirectedErrorCode(int iErrorCode)
        {
            return BufferSink.MapErrorCodeToMessage(iErrorCode);
        }

        #endregion

        #region Helper methods

        /// <summary>
        /// Creates a new auditingconfiguration which contains the current
        /// component settings relating to the log auditing
        /// </summary>
        /// <returns>The AuditingConfiguration relating to the log auditing</returns>
        private AuditingConfiguration CurrentLogAuditingConfig()
        {
            bool hasLogAuditing = GetComponentPropertyValue<bool>(PropertiesManager.IncludeLogOutputPropName);
            bool includeLogValidationMessages = GetComponentPropertyValue<bool>(PropertiesManager.IncludeValidationMessagesPropName);
            bool includeLogSourceData = GetComponentPropertyValue<bool>(PropertiesManager.IncludeLogSourceDataPropName);
            DataType logSourceDataType = GetComponentPropertyValue <DataType>(PropertiesManager.LogSourceDataTypePropName);
            int logSourceDataSize = GetComponentPropertyValue<int>(PropertiesManager.LogSourceDataSizePropName);

            return new AuditingConfiguration()
                {
                    HasAuditing = hasLogAuditing,
                    IncludeValidationMessages = includeLogValidationMessages,
                    IncludeSourceData = includeLogSourceData,
                    SourceDataType = logSourceDataType,
                    SourceDataSize = logSourceDataSize
                };
        }

        /// <summary>
        /// Creates a new auditingconfiguration which contains the current
        /// component settings relating to the error auditing
        /// </summary>
        /// <returns>The AuditingConfiguration relating to the error auditing</returns>
        private AuditingConfiguration CurrentErrorAuditingConfig()
        {
            bool hasErrorAuditing = GetComponentPropertyValue<bool>(PropertiesManager.IncludeErrorAuditRowsPropName);
            bool includeErrorValidationMessages = GetComponentPropertyValue<bool>(PropertiesManager.IncludeErrorValidationMessagesPropName);
            bool includeErrorSourceData = GetComponentPropertyValue<bool>(PropertiesManager.IncludeErrorSourceDataPropName);
            DataType errorSourceDataType = GetComponentPropertyValue<DataType>(PropertiesManager.ErrorSourceDataTypePropName);
            int errorSourceDataSize = GetComponentPropertyValue<int>(PropertiesManager.ErrorSourceDataSizePropName);

            return new AuditingConfiguration()
                {
                    HasAuditing = hasErrorAuditing,
                    IncludeValidationMessages = includeErrorValidationMessages,
                    IncludeSourceData = includeErrorSourceData,
                    SourceDataType = errorSourceDataType,
                    SourceDataSize = errorSourceDataSize
                };
        }

        /// <summary>
        /// Creates a new BufferSink using the current component configuration
        /// </summary>
        /// <param name="output">The standard output to associate the buffersink with</param>
        /// <param name="parser">The parser providing details on number of rows to skip</param>
        private BufferSink CreateBufferSink(IDTSOutput90 output, DelimitedFileParser parser)
        {
            bool firstRowColumnNames = GetComponentPropertyValue<bool>(PropertiesManager.ColumnNamesInFirstRowPropName);
            bool treatNulls = GetComponentPropertyValue<bool>(PropertiesManager.TreatEmptyStringsAsNullPropName);
            bool includeErrorAuditRows = GetComponentPropertyValue<bool>(PropertiesManager.IncludeErrorAuditRowsPropName);
            bool trimWhiteSpace = GetComponentPropertyValue<bool>(PropertiesManager.TrimWhitespacePropName);
            
            ComponentBufferService bufferService = new ComponentBufferService(_mainOutputProps, _logOutputProps, _errorOutputProps);
            bufferService.OutputEncoding = Encoding.GetEncoding(GetComponentPropertyValue<int>(PropertiesManager.CodePagePropName));
            bufferService.Locale = ComponentMetaData.LocaleID;
            bufferService.ErrorAuditingConfig = CurrentErrorAuditingConfig();
            bufferService.LogAuditingConfig = CurrentLogAuditingConfig();

            BufferSink bufferSink = new BufferSink(bufferService, output, treatNulls, includeErrorAuditRows, trimWhiteSpace);
            bufferSink.CurrentRowCount = parser.HeaderRowsToSkip + parser.DataRowsToSkip + (firstRowColumnNames ? 1 : 0);
            bufferSink.RowDelimiter = parser.RowDelimiter;

            string configuredColumns = GetComponentPropertyValue<string>(PropertiesManager.ConfiguredFileColumnsPropName);
            bufferSink.DefineStandardOutputColumns(CommonUtils.DeserializeConfiguredFileColumns(configuredColumns));

            return bufferSink;
        }

        /// <summary>
        /// Adds the default columns required for the error output
        /// </summary>
        /// <param name="errorOutput">The error output to add the columns to</param>
        private void AddErrorOutputColumns(IDTSOutput90 errorOutput)
        {
            CommonUtils.AddOutputColumn(errorOutput, SourceColumnName, DataType.DT_STR, 255, 0, 0, PropertiesManager.DefaultCodePage);
            AuditingConfiguration errorAuditingConfig = CurrentErrorAuditingConfig();

            if (errorAuditingConfig.HasAuditing)
            {
                CommonUtils.ConfigureErrorOutputAuditing(errorOutput, true, errorAuditingConfig.IncludeValidationMessages,
                    errorAuditingConfig.IncludeSourceData, errorAuditingConfig.SourceDataType, errorAuditingConfig.SourceDataSize);
            }
        }

        /// <summary>
        /// Adds the log output columns based on the current settings
        /// </summary>
        /// <param name="logOutput">The log output to add the columns to</param>
        private void AddLogOutputColumns(IDTSOutput90 logOutput)
        {
            AuditingConfiguration logAuditingConfig = CurrentLogAuditingConfig();
            CommonUtils.ConfigureLogOutput(logOutput, logAuditingConfig.IncludeValidationMessages, logAuditingConfig.IncludeSourceData,
                logAuditingConfig.SourceDataType, logAuditingConfig.SourceDataSize);
        }

        /// <summary>
        /// Gets the value of a custom property
        /// </summary>
        /// <param name="propertyName">The name of the custom property to get the value from</param>
        /// <returns>The value of the custom property</returns>
        private T GetComponentPropertyValue<T>(string propertyName)
        {
            try
            {
                return PropertiesManager.GetPropertyValue<T>(this.ComponentMetaData.CustomPropertyCollection, propertyName);
            }
            catch
            {
                PostErrorAndThrow(MessageStrings.CantFindProperty(propertyName));
                return default(T);
            }
        }

        /// <summary>
        /// Fires an error message to the container hosting the component within SSIS
        /// </summary>
        /// <param name="errorMessage">The error message to fire</param>
        /// <remarks>
        /// Errors should almost always be fatal but the 
        /// host container could be configured to allow them
        /// </remarks>
        private void PostError(string errorMessage)
        {
            bool cancelled;
            ComponentMetaData.FireError(_E_FAIL, ComponentMetaData.Name, errorMessage, string.Empty, 0, out cancelled);
        }

        /// <summary>
        /// Fires an error message and forces the component to exit by throwing an exception
        /// </summary>
        /// <param name="errorMessage">The error message to fire</param>
        private void PostErrorAndThrow(string errorMessage)
        {
            PostError(errorMessage);
#if !DEBUG
            throw new COMException(errorMessage, _E_FAIL);
#endif
        }

        /// <summary>
        /// Validates all the custom property settings on the component
        /// </summary>
        /// <param name="oldStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateComponentProperties(DTSValidationStatus oldStatus)
        {
            return _propertiesManager.ValidateProperties(this.ComponentMetaData.CustomPropertyCollection, oldStatus);
        }

        /// <summary>
        /// Validates that there are no inputs set up on the component
        /// </summary>
        /// <param name="oldStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateInputs(DTSValidationStatus oldStatus)
        {
            if (this.ComponentMetaData.InputCollection.Count > 0)
            {
                PostError(MessageStrings.NotExpectedInputs);
                return DTSValidationStatus.VS_ISCORRUPT;
            }
            
            return oldStatus;
        }

        /// <summary>
        /// Validates that the correct number of outputs are setup
        /// and configured
        /// </summary>
        /// <param name="oldStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateOutputs(DTSValidationStatus oldStatus)
        {
            DTSValidationStatus returnStatus = oldStatus;
            if (this.ComponentMetaData.OutputCollection.Count < 1 || this.ComponentMetaData.OutputCollection.Count > 3)
            {
                PostError(MessageStrings.UnexpectedNumberOfOutputs);
                returnStatus = DTSValidationStatus.VS_ISCORRUPT;
            }
            else
            {
                IDTSOutputCollection90 outputCollection = this.ComponentMetaData.OutputCollection;
                
                foreach (IDTSOutput90 output in outputCollection)
                {
                    switch (output.Name)
                    {
                        case MessageStrings.OutputName:
                            returnStatus = ValidateRegularOutput(output, returnStatus);
                            break;
                        case MessageStrings.LogOutputName:
                            returnStatus = ValidateLogOutput(output, returnStatus);
                            break;
                        case MessageStrings.ErrorOutputName:
                            returnStatus = ValidateErrorOutput(output, returnStatus);
                            break;
                    }
                }
            }

            return returnStatus;
        }

        /// <summary>
        /// Validates the standard output is setup correctly
        /// </summary>
        /// <param name="output">The standard output to validate</param>
        /// <param name="oldStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateRegularOutput(IDTSOutput90 output, DTSValidationStatus oldStatus)
        {
            DTSValidationStatus returnStatus = oldStatus;

            IDTSOutputColumnCollection90 outputColumnCollection = output.OutputColumnCollection;

            if (outputColumnCollection.Count == 0)
            {
                PostError(MessageStrings.NoOutputColumns);
                if (returnStatus != DTSValidationStatus.VS_ISCORRUPT)
                {
                    returnStatus = DTSValidationStatus.VS_ISBROKEN;
                }
            }
            else
            {
                returnStatus = ValidateOutputColumns(outputColumnCollection, returnStatus);
            }

            return returnStatus;
        }

        /// <summary>
        /// Validates the log output is setup correctly
        /// </summary>
        /// <param name="logOutput">The log output to validate</param>
        /// <param name="oldStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateLogOutput(IDTSOutput90 logOutput, DTSValidationStatus oldStatus)
        {
            DTSValidationStatus returnStatus = oldStatus;

            IDTSOutputColumnCollection90 outputColumnCollection = logOutput.OutputColumnCollection;

            if (outputColumnCollection.Count == 0)
            {
                PostError(MessageStrings.NoLogOutputColumns);
                returnStatus = DTSValidationStatus.VS_ISCORRUPT;
            }

            return returnStatus;
        }

        /// <summary>
        /// Validates the error output is setup correctly
        /// </summary>
        /// <param name="errorOutput">The error output to validate</param>
        /// <param name="oldStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateErrorOutput(IDTSOutput90 errorOutput, DTSValidationStatus oldStatus)
        {
            DTSValidationStatus returnStatus = oldStatus;

            IDTSOutputColumnCollection90 outputColumnCollection = errorOutput.OutputColumnCollection;

            if (outputColumnCollection.Count == 0)
            {
                PostError(MessageStrings.NoErrorOutputColumns);
                returnStatus = DTSValidationStatus.VS_ISCORRUPT;
            }

            return returnStatus;
        }

        /// <summary>
        /// Validates that a collection of output columns
        /// is correctly configured
        /// </summary>
        /// <param name="outputColumnCollection">The collection of output columns to validate</param>
        /// <param name="oldStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateOutputColumns(IDTSOutputColumnCollection90 outputColumnCollection, DTSValidationStatus oldStatus)
        {
            DTSValidationStatus returnStatus = oldStatus;

            foreach (IDTSOutputColumn90 outputColumn in outputColumnCollection)
            {
                returnStatus = ValidateOutputColumn(outputColumn, returnStatus);
            }

            return returnStatus;
        }

        /// <summary>
        /// Validates an output column to confirm it is correctly configured
        /// </summary>
        /// <param name="outputColumn">The output column to validate</param>
        /// <param name="returnStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateOutputColumn(IDTSOutputColumn90 outputColumn, DTSValidationStatus returnStatus)
        {
            returnStatus = _propertiesManager.ValidateProperties(outputColumn.CustomPropertyCollection, returnStatus);

            if (returnStatus != DTSValidationStatus.VS_ISCORRUPT)
            {
                DTSValidationStatus newValidationStatus = ValidateSupportedDataTypes(outputColumn.DataType);
                returnStatus = CommonUtils.CompareValidationValues(returnStatus, newValidationStatus);
            }

            return returnStatus;
        }

        /// <summary>
        /// Validates the connection to the source file using 
        /// the FILE connection manager 
        /// </summary>
        /// <param name="oldStatus">The status of the previous validation</param>
        /// <returns>The new validation status</returns>
        private DTSValidationStatus ValidateConnection(DTSValidationStatus oldStatus)
        {
            if (this.ComponentMetaData.RuntimeConnectionCollection.Count != 1)
            {
                PostError(MessageStrings.InvalidConnectionReference(_fileConnectionName));
                return DTSValidationStatus.VS_ISCORRUPT;
            }
            
            IDTSRuntimeConnection90 connection = this.ComponentMetaData.RuntimeConnectionCollection[0];
            if (string.Compare(connection.Name, _fileConnectionName, StringComparison.OrdinalIgnoreCase) != 0)
            {
                PostError(MessageStrings.InvalidConnectionReference(_fileConnectionName));
                return DTSValidationStatus.VS_ISCORRUPT;
            }

            return oldStatus;
        }

        /// <summary>
        /// Validates the data type is supported for processing
        /// </summary>
        /// <param name="dataType">The datatype to validate</param>
        /// <returns>The validation status</returns>
        private DTSValidationStatus ValidateSupportedDataTypes(DataType dataType)
        {
            if (dataType == DataType.DT_BYTES ||
                dataType == DataType.DT_IMAGE)
            {
                PostError(MessageStrings.UnsupportedDataType(dataType.ToString()));
                return DTSValidationStatus.VS_ISCORRUPT;
            }
            
            return DTSValidationStatus.VS_ISVALID;
        }

        /// <summary>
        /// Returns the current encoding setting of the component
        /// </summary>
        /// <returns>The Encoding value of the component</returns>
        private Encoding GetEncoding()
        {
            bool unicode = GetComponentPropertyValue<bool>(PropertiesManager.IsUnicodePropName);
            int codePage = GetComponentPropertyValue<int>(PropertiesManager.CodePagePropName);

            System.Text.Encoding encoding = unicode ? Encoding.Unicode : Encoding.ASCII;

            if (!unicode && codePage > 0)
            {
                encoding = Encoding.GetEncoding(codePage, new EncoderReplacementFallback("?"), new DecoderReplacementFallback("?"));
            }

            return encoding;
        }

        #endregion
    }
}